package dadonas.xmlpersistence;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
*
* Used to write the XML file. 
* 
* @author dadonas
* 
* @since 2011
* 
*/

@SuppressWarnings({"rawtypes","unchecked"})
public class XMLPersistenceWriter {
	
	private String directory;
	private String fileName;
	private StringBuffer xmlBody;
	
	/**
	 * 
	 * @param diretory The path of the directory. Ex.: /my_xml_files
	 * @param fileName The xml file name. Ex.: my_xml.xml
	 * @param setVersionEncoding Sets the XML header with version and encoding
	 */
	public XMLPersistenceWriter(String diretory, String fileName, boolean setVersionEncoding) {
		super();
		this.directory = diretory;
		this.fileName = fileName;
		xmlBody = new StringBuffer();
		if (setVersionEncoding){
			xmlBody.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
		}
	}
	
	
	/**
	 * Add an object to the XML file.
	 * @param obj The to add into the XML.
	 */
	public void addObject(Object obj){
		xmlBody.append(internalParse(obj,null));
	}
	
	/**
	 * Add a list of objects in the XML file.
	 * @param objs A list of objects to add into the XML.
	 */
	public void addObjects(List objs){
		for(Object obj : objs){
			addObject(obj);
		}
	}
	
	/**
	 * Creates the XML file.
	 */
	public void persist(){
		try {
			File file = openFile();
			
			BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));
			writer.write(xmlBody.toString().replace("&", "&amp;"));
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private String internalParse(Object bean, String classTagName){
		StringBuffer returnXML = new StringBuffer();
		
		if (bean!=null){
			SortedMap<Integer,String> xml = new TreeMap<Integer, String>();
			Class klass = bean.getClass();
			String classTag = klass.getSimpleName();
			SortedMap<Integer,String> attributes = new TreeMap<Integer, String>();
			boolean showWhenNull = true;
			
			if (classTagName != null && !classTagName.isEmpty()){
				classTag = classTagName;
			} else{
				XMLPersistenceType xmlPerAnn = (XMLPersistenceType) klass.getAnnotation(XMLPersistenceType.class);
		    	
		    	if (xmlPerAnn != null){
		    		classTag = xmlPerAnn.name();
		    	}
			}
	
	        Map<Field, Object> fieldsMap = getFields(bean);
	        
	        for (Field field : fieldsMap.keySet()){
	        	if (field.getAnnotation(XMLPersistenceIgnore.class) != null){
	        		continue;
	        	}
	        	
	        	FieldType fieldType = getFieldType(field);
	        	Object value = fieldsMap.get(field);
	        	
	        	String tagName = field.getName();
	        	String tagValue = "";	 
	        	boolean isAttribute = false;
	        	boolean isType = false;
	        	int order = 0;
	        	
	        	XMLPersistenceField fieldAnnotation = field.getAnnotation(XMLPersistenceField.class);
	        	
	        	if (fieldAnnotation != null){
	        		if (!fieldAnnotation.name().isEmpty()){
	        			tagName = fieldAnnotation.name();
	        		}
	        		order = fieldAnnotation.order();
            		isAttribute = fieldAnnotation.isAttibute();
            		showWhenNull = fieldAnnotation.minOccurs() == 0 ? false : true;
            		//TODO Validar se campo possui minOccurs e se valor foi informado
            	}
	        	
	        	XMLPersistenceString stringAnnotation = field.getAnnotation(XMLPersistenceString.class);
	        	XMLPersistenceInteger intAnnotation = field.getAnnotation(XMLPersistenceInteger.class);
	        	XMLPersistenceDate dateAnnotation = field.getAnnotation(XMLPersistenceDate.class);
	        	XMLPersistenceDouble doubleAnnotation = field.getAnnotation(XMLPersistenceDouble.class);
	        	XMLPersistenceCollection collectionAnnotation = field.getAnnotation(XMLPersistenceCollection.class);
	        	
	        	switch (fieldType){
		        	case STRING:
		        		if (stringAnnotation != null){
		        			//TODO validate maxchars
		            	}
		        		
		        		if (value != null){
	            			tagValue = value.toString();
	            		} break;
	
		        	case INTEGER:
		        		String intFormat = "";
		        		if (intAnnotation != null){
		        			intFormat = intAnnotation.intFormat();
		            	}
		        		
		        		if (value != null){
		        			if (!intFormat.isEmpty()){
		        				DecimalFormat df = new DecimalFormat(intFormat);
			            		tagValue = df.format(value);
		        			} else{
		        				tagValue = value.toString();
		        			}
	            		} break;
		        		
		        	case DECIMAL:
		        		int fieldDecimals = 0;
		        		
		        		if (doubleAnnotation != null){
		        			fieldDecimals = doubleAnnotation.decimals();
		            	}
		        		
		        		if (value != null){
	            			String decimals = "";
		            		
		            		for (int i=0;i<fieldDecimals;i++){
		            			decimals += "0";
		            		}
		            		
		            		DecimalFormat df = new DecimalFormat("."+decimals);
		            		tagValue = df.format(value).replace(",", ".");
	            		} break;
		        	
		        	case DATE:
		        		DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
		        		
		        		if (dateAnnotation != null){
		        			df = new SimpleDateFormat(dateAnnotation.dateFormat());
		        		}
		        		
		        		if (value != null){
		        			tagValue = df.format(value);
	            		} break;
		        		
		        	case ARRAY:
		        		String childATagName = "";
		        		
		        		if (collectionAnnotation != null){
		        			childATagName = collectionAnnotation.childTagName();
		        			isType = collectionAnnotation.noTagForCollection();
		        		}
		        		
		        		if (value != null){
	            			Object array[] = (Object[]) value;
	            			for (int ar=0; ar < Array.getLength(array); ar++){
                        		Object obj = Array.get(array, ar);
                        		tagValue += internalParse(obj,childATagName);
                        	}
	            		}
		        		
		        		isAttribute = false;
		        		
		        		break;
		        	
		        	case COLLECTION:
		        		String childTagName = "";
		        		
		        		if (collectionAnnotation != null){
		        			childTagName = collectionAnnotation.childTagName();
		        			isType = collectionAnnotation.noTagForCollection();
		        		}
		        		
		        		if (value != null){
            				List list = (List) value;
                            for (Object obj : list){
                            	tagValue += internalParse(obj,childTagName);
                        	}
	            		}
		        		
		        		isAttribute = false;
		        		
		        		break;
		        	
		        	case TYPE:
		        		XMLPersistenceType typeAnnotation = field.getAnnotation(XMLPersistenceType.class);
		        		
		        		if (typeAnnotation != null){
		        			tagName = typeAnnotation.name();
		        		} 
		        		
		        		if (value != null){
		        			if (typeAnnotation == null){
		        				tagName = "";
		        			}
		        			
                           	tagValue = internalParse(value,tagName);
	            		}
		        		
		        		isType = true;
		        		
		        		break;
	        	}
	        	
	        	if (value == null && fieldAnnotation != null && !fieldAnnotation.defaultValue().isEmpty()){
	        		value = fieldAnnotation.defaultValue().toString();
	        	}
	        	
	        	if (tagValue.isEmpty()){
	        		if (showWhenNull){
	        			if (order >= 0){
	        				xml.put(order,"  <"+tagName+" />\n");
			        	} else{
			        		xml.put(xml.size(),"  <"+tagName+" />\n");
			        	}	        			
	        		}
		        } else if (isAttribute){
		        	if (order >= 0){
		        		attributes.put(order, " "+tagName+"=\""+tagValue+"\"");
		        	} else{
		        		attributes.put(attributes.size()," "+tagName+"=\""+tagValue+"\"");
		        	}
		        } else if (isType){
		        	if (order >= 0){
		        		xml.put(order,"  "+tagValue+"\n"); 
		        	} else{
		        		xml.put(xml.size(),"  "+tagValue+"\n"); 
		        	}
		        } else{
		        	if (order >= 0){
		        		xml.put(order,"  <"+tagName+">"+tagValue+"</"+tagName+">\n");
		        	} else{
		        		xml.put(xml.size(),"  <"+tagName+">"+tagValue+"</"+tagName+">\n");
		        	}
		        }
	        }
	        
	        returnXML.append("<"+classTag);
	        
	        for (String att : attributes.values()){
	        	returnXML.append(att);
	        }
	        
	        returnXML.append(">\n");
	        
	        for (String tag : xml.values()){
	        	returnXML.append(tag);
	        }
	        
	        returnXML.append("</"+classTag+">\n");
		}
		
		return returnXML.toString();
	}
	
	private Map<Field, Object> getFields(Object bean){
		Class klass = bean.getClass();
		
		Map<Field, Object> fieldsMap = new HashMap<Field, Object>();
		
		Method[] methods = klass.getMethods();
		
		for (int i = 0; i < methods.length; i += 1) {
            try {
                Method method = methods[i];
                if (Modifier.isPublic(method.getModifiers()) && !method.getName().equals("getClass")) {
                    String name = method.getName();
                    String key = "";
                    if (name.startsWith("get")) {
                        key = name.substring(3);
                    } else if (name.startsWith("is")) {
                        key = name.substring(2);
                    }
                    
                    if (key.length() > 0 && Character.isUpperCase(key.charAt(0)) && method.getParameterTypes().length == 0) {
                        if (key.length() == 1) {
                        	key = key.toLowerCase();
                        } else if (!Character.isUpperCase(key.charAt(1))) {
                        	key = key.substring(0, 1).toLowerCase() +
                                key.substring(1);
                        }
                        
                        Field field = klass.getDeclaredField(key);	                        	

                        Object result = method.invoke(bean,(Object[])null);
                        fieldsMap.put(field, result);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
		
		return fieldsMap;
	}
	
	private FieldType getFieldType(Field field){
		FieldType type = FieldType.TYPE;
		
		String fieldType = field.getType().getSimpleName();
		
		if (fieldType.equals("char") ||
			fieldType.equals("String") ||
			field.getType().isEnum()){
			type = FieldType.STRING;
		} else if (fieldType.equals("byte") ||
			fieldType.equals("short") ||
			fieldType.equals("int") || 
			fieldType.equals("Byte") || 
			fieldType.equals("Short") ||
			fieldType.equals("Integer") ||
			fieldType.equals("BigInteger")){
			type = FieldType.INTEGER;
		} else if (fieldType.equals("long") ||
			fieldType.equals("float") ||
			fieldType.equals("double") || 
			fieldType.equals("Long") || 
			fieldType.equals("Float") ||
			fieldType.equals("Double")){
			type = FieldType.DECIMAL;
		} else if (fieldType.equals("Date")){
			type = FieldType.DATE;
		} else if (fieldType.contains("[]")){
			type = FieldType.ARRAY;
		} else if (fieldType.equals("List")){
			type = FieldType.COLLECTION;
		}
		
		return type;
	}
	
	private File openFile(){
		File dir = new File(directory);
		
		if (!dir.exists()){
			dir.mkdir();
		}
		
		File xml = new File(directory+"/"+fileName);
		
		if (xml.exists()){
			xml.delete();
		}
		
		try {
			xml.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return xml;
	}
	
}
